﻿ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using NeurOthello.NNetwork;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;


namespace NeurOthello.Logic
{
    public class NeuroPlayer : Player
    {       
        private List<LinkedList<double[]>> PlayerHistory = new List<LinkedList<double[]>>();

        public PlayerSettings Set { get; set; }
        private Othello othello;         
        
        public bool IsCpu { get { return true; } }

        public NeuroPlayer(Othello o, PlayerSettings set)
        {
            othello = o;
            this.Set = set;
            PlayerHistory.Add(new LinkedList<double[]>());
            PlayerHistory.Add(new LinkedList<double[]>());
        }

        public Position MakeTrainingMove(int playerId)
        {
            TDTraining(playerId, Set.Network.ProcessInput(othello.Inputboard));

            NetworkMove[] evalMoves = null;
            NetworkMove best = EvaluateAllMoves(ref evalMoves, playerId);

            NetworkMove res = Set.RandomExploration(evalMoves, best);

            Set.Network.ProcessInput(othello.Inputboard, res.Value, Set.Learning * 0.5);
            
            return res;
        }

        private void TDTraining(int playerId, double[] trainingValue)
        {
            LinkedListNode<double[]> prevState = PlayerHistory[playerId-1].First;            
            int i = 0;
            while (prevState != null)
            {
                Set.Network.ProcessInput(prevState.Value, trainingValue, Set.Learning * Math.Pow(Set.Lambda, i));
                i++;
                if (i == Set.TDLimit)
                    break;
                prevState = prevState.Next;
            }
            PlayerHistory[playerId-1].AddFirst(othello.Inputboard);
        }

        public Position MakeMove(int playerId)
        {           
            NetworkMove[] evalMoves = null;
            NetworkMove best = EvaluateAllMoves(ref evalMoves, playerId);            

            return Set.RandomTopExploration(evalMoves, best, playerId);
        }

        private NetworkMove EvaluateAllMoves(ref NetworkMove[] evaluatedMoves, int playerId)
        {
            Position[] moves = othello.AllMoves(playerId);
            evaluatedMoves = new NetworkMove[moves.Length];         
            NetworkMove best = null;
            int i = 0;
            foreach (Position pos in moves)
            {
                Othello newState = new Bitboard((Bitboard)othello);
                newState.PlaceMarker(playerId, pos);               

                if (best == null)
                {
                    best = new NetworkMove(pos, Set.Network.ProcessInput(newState.Inputboard));
                    evaluatedMoves[i] = best;
                }
                else
                {
                    evaluatedMoves[i] = new NetworkMove(pos, Set.Network.ProcessInput(newState.Inputboard));
                    if (evaluatedMoves[i].Advantage(playerId) > best.Advantage(playerId))
                    {
                        best = evaluatedMoves[i];
                    }
                }
                i++;
            }
            return best;
        }

        public void TrainPlayer(double[] result)
        {
            double[] input = othello.Inputboard;

            Set.Network.ProcessInput(input, result, Set.Learning);

            TDTraining(1, result);
            PlayerHistory[0].Clear();
            TDTraining(2, result);
            PlayerHistory[1].Clear();
        }      

        public void SetGame(Othello game)
        {
            this.othello = game;
        }
    }
}
